// =======================================================================
// 
//       Filename:  parser.cpp
// 
//    Description:  
// 
//        Version:  1.0
//        Created:  26.03.2010 19:52:09
//       Revision:  none
//       Compiler:  g++
// 
//         Author:  Dominik 'domachine' Burgdörfer (-), dominik.burgdoerfer@googlemail.com
//        Company:  -
// 
// =======================================================================

#include "parser.hpp"
#include "citerator.hpp"
#include <boost/regex.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <fstream>
#include "template/commandelement.hpp"
#include "types/float.hpp"
#include "types/integer.hpp"

// Some utilities
namespace {
    using namespace webplodder;

    bool regexIterMatch(CIterator& iter, 
            const boost::regex& regex) {
        std::string str;

        str.push_back(iter.currentChar());
        return boost::regex_match(str, regex);
    }
}

namespace webplodder {
    Parser::Parser()
        : mLine(0)
    {
        
    }

    void Parser::parse(Template::File& templateFile)
       
    {
        std::ifstream input(templateFile.sourcePath().string().c_str());
        mIterator.setStream(input);
        mLine = 1;
        bool textBlock = false;
        Template::TextElement* textElement = 0;

        for(; mIterator.good(); mIterator.moveForward()) {
            if(mIterator.currentChar() != '{') {
                if(!textBlock) {
                    textElement = new Template::TextElement(templateFile,
                            mIterator.position(), -1);

                    textBlock = true;
                }

                if(mCacheMode == CACHE_ALL)
                    (*textElement) << mIterator.currentChar();
                if(mIterator.currentChar() == '\n')
                    ++mLine;
            }
            else {
                if(mIterator.nextChar() == '{') {
                    if(mCacheMode == CACHE_ALL)
                        (*textElement) << mIterator.currentChar();
                    safelyIncr();
                    continue;
                }

                if(textBlock) {
                    textElement->setEnd(mIterator.position());
                    templateFile.push_back(
                            boost::shared_ptr<Template::TextElement>(
                                textElement));
                    textBlock = false;
                }

                templateFile.push_back(parseCmd(templateFile));
            }
        }

        if(textElement->end() == -1) {
            textElement->setEnd(mIterator.position());
            templateFile.push_back(
                    boost::shared_ptr<Template::TextElement>(
                        textElement));
        }
    }

    CIterator* Parser::iterator()
    {
        return &mIterator;
    }

    void Parser::setCacheMode(CacheMode mode)
    {
        mCacheMode = mode;
    }

    void Parser::safelyIncr()
    {
        mIterator.moveForward();

        if(!mIterator.good())
            throw SyntaxError("Unexpected end-of-stream.",
                    mLine);
    }

    void Parser::skipWhiteSpace()
    {
        boost::regex spaceregex("[ \t\n]");

        while(regexIterMatch(mIterator, spaceregex)) {
            if(mIterator.currentChar() == '\n')
                ++mLine;

            safelyIncr();
        }
    }

    boost::shared_ptr<Template::ScriptElement> Parser::parseScript(
            Template::File& templateFile)
       
    {
        boost::regex scriptregex("[a-z0-9\\-_A-Z]");
        std::string scriptSpec;
        boost::shared_ptr<Template::ScriptElement> scriptElement( 
            new Template::ScriptElement(templateFile));

        safelyIncr();

        skipWhiteSpace();

        while(regexIterMatch(mIterator, scriptregex)) {
            scriptSpec.push_back(mIterator.currentChar());
            safelyIncr();
        }

        if(scriptSpec.size() == 0)
            throw SyntaxError("No script specification",
                    mLine);

        scriptElement->setLine(mLine);

        skipWhiteSpace();

        scriptElement->setBegin(mIterator.position());

        while(true) {
            if(mIterator.currentChar() == '!' && 
                    mIterator.nextChar() == '}') {
                safelyIncr();
                scriptElement->setEnd(mIterator.position());
                break;
            }
            else {
                char cChar = mIterator.currentChar();
                if(mCacheMode == CACHE_ALL || mCacheMode == CACHE_SCRIPT)
                    (*scriptElement) << cChar;
                if(cChar == '\n')
                    mLine++;
                safelyIncr();
            }
        }

        scriptElement->setSpecification(scriptSpec);

        //if(mIterator.nextChar() == '\n') {
        //    safelyIncr();
        //    mLine++;
        //}

        return scriptElement;
    }

    boost::shared_ptr<Template::VarElement> Parser::parseVar(Template::File& templateFile)
    {
        std::string cmdName;
        boost::regex cmdregex("[a-zA-Z_0-9]");

        safelyIncr();
        skipWhiteSpace();
        while(regexIterMatch(mIterator, cmdregex)) {
            cmdName.push_back(mIterator.currentChar());
            safelyIncr();
        }

        boost::shared_ptr<Template::VarElement> varElement(
            new Template::VarElement(templateFile));
        varElement->setName(cmdName);
        varElement->setLine(mLine);

        skipWhiteSpace();

        if(mIterator.currentChar() != '}')
            throw SyntaxError(
                    "unexpected char, expected '}'",
                    mLine);
        return varElement;
    }

    boost::shared_ptr<Template::Element> 
        Parser::parseCmd(Template::File& templateFile) 
    {
        std::string cmdName;
        int cmdLine;
        bool braceOpen = false;
        boost::regex cmdregex("[a-zA-Z_0-9]");

        braceOpen = true;
        safelyIncr();

        skipWhiteSpace();

        if(mIterator.currentChar() == '!') {
            return parseScript(templateFile);
        }
        else if(mIterator.currentChar() == '$') {
            return parseVar(templateFile);
        }

        while(regexIterMatch(mIterator, cmdregex)) {
            cmdName.push_back(mIterator.currentChar());
            safelyIncr();
        }

        if(cmdName.size() == 0)
            throw SyntaxError("no or no valid command name",
                    mLine);

        cmdLine = mLine;
        
        skipWhiteSpace();

        std::vector<boost::shared_ptr<types::Object> > args;

        if(mIterator.currentChar() == ':') {
            safelyIncr();

            skipWhiteSpace();

            std::string currentArg;
            while(true) {
                if(mIterator.currentChar() == '"' ||
                        mIterator.currentChar() == '\'') {
                    args.push_back(parseString());
                    safelyIncr();
                    skipWhiteSpace();
                }
                else if(mIterator.currentChar() == '{') {
                    args.push_back(parseCmd(
                                templateFile));

                    safelyIncr();
                    skipWhiteSpace();
                }
                else if(mIterator.currentChar() == '}')
                    break;
                else if(mIterator.currentChar() == ',') {
                    safelyIncr();
                    skipWhiteSpace();
                }
                else {
                    args.push_back(parseNumber());
                    skipWhiteSpace();
                }
            }
        }
        else if(mIterator.currentChar() != '}') {
            throw SyntaxError(
                    "unexpected char, expected '}' or ':'",
                    mLine);
        }

        if(cmdName == "set" && mIterator.nextChar() == '\n') {
            safelyIncr();
            mLine++;
        }

        boost::shared_ptr<Template::CommandElement> element(
            new Template::CommandElement(templateFile, 
                    cmdName, args));

        element->setLine(cmdLine);
        return element;
    }

    boost::shared_ptr<types::String> Parser::parseString() {
        char delimiter = '"';
        boost::shared_ptr<types::String>
            buffer(new types::String(""));

        if(mIterator.currentChar() == '\'')
            delimiter = '\'';

        char charBeforeLast;

        do {
            charBeforeLast = mIterator.lastChar();
            safelyIncr();

            if(mIterator.currentChar() == '\\')
                if(mIterator.lastChar() != '\\')
                    continue;

            if(mIterator.currentChar() == delimiter)
                if(mIterator.lastChar() != '\\' || charBeforeLast == '\\')
                    break;

            buffer->push_back(mIterator.currentChar());

            if(mIterator.currentChar() == '\n') {
                if(mIterator.lastChar() == '\\' &&
                        charBeforeLast != '\\')
                    buffer->erase(buffer->end()-1);
                ++mLine;
            }
        } while(true);

        return buffer;
    }

    boost::shared_ptr<types::Object> Parser::parseNumber() {
        boost::regex numRegex("[0-9\\.\\-]");
        std::string buffer;

        while(regexIterMatch(mIterator, numRegex)) {
            buffer.push_back(mIterator.currentChar());
            safelyIncr();
        }

        if(buffer.empty())
            throw SyntaxError("unexpected token", 
                              mLine);
        else {
            boost::regex floatRegex("\\-?[0-9]+\\.[0-9]+");
            boost::regex intRegex("\\-?[0-9]+");

            if(boost::regex_match(buffer, floatRegex)) {
                double f = atof(buffer.c_str());

                if(f == HUGE_VAL || f == -HUGE_VAL)
                    throw SyntaxError("double value out of range",
                            mLine);
                
                boost::shared_ptr<types::Float> floatObj(
                        new types::Float(f));

                return floatObj;
            }
            else if(boost::regex_match(buffer, intRegex)) {
                int i = atoi(buffer.c_str());

                if(i == INT_MAX || i == INT_MIN)
                    throw SyntaxError("integer value out of range",
                            mLine);

                boost::shared_ptr<types::Integer> 
                        integer(new types::Integer(i));
                return integer;
            }
            else 
                throw SyntaxError("unexpected token", 
                              mLine);
        }
    }
}
